package euler.helpers;

import static org.jlambda.Context._;

import java.util.ArrayList;

import org.jlambda.functions.Fun1;
import org.jlambda.functions.Fun2;
import org.jlambda.functions.Fun3;
import org.jlambda.functions.Fun4;
import org.jlambda.tuples.Tuple2;
import org.jlambda.util.Logic;
import org.jlambda.util.Math;

public class Points {
	public static class Point extends Tuple2<Number, Number>{
		public Point(Number p1, Number p2) {
			super(p1, p2);			
		}
		
		@Override
		public Point apply(Number p1, Number p2) {
			return new Point(p1, p2);
		}
	}
	
	
	public static final Logic.Echo<Number> echo = new Logic.Echo<Number>();  
	
	public static class PointList extends ArrayList<Point> {

		private static final long serialVersionUID = 1L;
		
	}
	
	public static final Fun1<Point, PointList> first = new Fun1<Point, PointList> (){
		public PointList apply(Point p1) {
			PointList list = new PointList();
			list.add(p1);
			return list;
		}
	};
	
	// functions which transform a point
	abstract public static class PointTransform extends Fun2<PointList, Point, Point> {
	}
	
	public static class PartsTransform extends PointTransform {

		private Fun1<Number, Number> xf;
		private Fun1<Number, Number> yf;

		public PartsTransform(Fun1<Number, Number> xf, Fun1<Number,Number> yf){
			this.xf = xf;
			this.yf = yf;
		}
		
		@Override
		public Point apply(PointList p1, Point p2) {
			return new Point(xf.apply(p2.get1()), yf.apply(p2.get2()));
		}
	}
	
	public static final PartsTransform trans(Fun1<Number, Number> xf, Fun1<Number,Number> yf){
		return new PartsTransform(xf, yf);
	}
	
	/**
	 * Apply this transform over N amount of steps
	 *
	 */
	public static class PatternStep extends Tuple2<PointTransform, Number>{
		public PatternStep(PointTransform p1, Number p2) {
			super(p1, p2);			
		}
	}
	
	public static final PatternStep pstep(PointTransform pt, long n){
		return new PatternStep(pt, n);
	}
	
	// functions which transform a PointList
	abstract public static class PointListTransform extends Fun1<PointList, PointList> {
	}
	
	// functions which add a step in a pattern
	public static final Fun2<PointList, PatternStep[], PointList> step = new Fun2<PointList, PatternStep[], PointList> (){
		public PointList apply(PointList p1, PatternStep[] p2) {
			// take the pointlist last, add to transform, add point
			Point point1 = p1.get(p1.size()-1);
			// remove the last
			p1.remove(p1.size() -1);
			Point next = point1;
			for (int i = 0; i < p2.length; i++) {
				
				// apply x times
				long size =p2[i].get2().longValue();
				for (long j = 0; j < size; j++) {
					next = p2[i].get1().apply(p1, next); 
					p1.add(next);
				}
			}
			return p1;
		}
		
	};

	static final public Fun1<PointList, PointList> step(PatternStep... p2) {
		return step.curry(_, p2);
	}
	
	/**
	 * diag \
	 */ 
	static final public Fun1<PointList, PointList> diagb(int steps){
		return Points.step(
			Points.pstep(Points.trans(Points.echo, Points.echo),1),
			Points.pstep(Points.trans(Math.inc, Math.inc),steps));
	}
	
	/**
	 * diag /
	 */
	static final public Fun1<PointList, PointList> diagf(int steps){
		return Points.step(
			Points.pstep(Points.trans(Math.add.curry(_,steps), Points.echo),1),
			Points.pstep(Points.trans(Math.dec, Math.inc),steps));
	}
	
	/**
	 * horiz
	 */
	static final public Fun1<PointList, PointList> horiz(int steps){
		return Points.step(
				Points.pstep(Points.trans(Points.echo, Points.echo),1),
				Points.pstep(Points.trans(Math.inc, Points.echo),steps));
	}
	
	/**
	 * vert
	 */
	static final public Fun1<PointList, PointList> vert(int steps){
		return Points.step(
				Points.pstep(Points.trans(Points.echo, Points.echo),1),
				Points.pstep(Points.trans(Points.echo, Math.inc),steps));
	}

	/**
	 * max numerical value comparisom function 
	 */
	public static final Fun2<Tuple2<Number, PointList>, Tuple2<Number, PointList>, Tuple2<Number, PointList>> listMax = 
			Tuple2.compare( Tuple2.<Number,PointList>func1()
				, Math.max);
	
	/**
	 * Accumalation operation on Points that map to arrays.  Used in folds.
	 * 
	 * Returns null if no matches for the point in the array.  If prev value is null then returns this value.
	 * 
	 * @param <T>
	 */
//	 function to prod elements in a point list, returns null if nothing matches in the list
	public static final class AccOpPointsArray<T> extends Fun4<Fun2<T,T,T>, Point, T[][], T, T>{
		@Override
		public T apply(Fun2<T,T,T> f, Point point, T[][] p2, T prev) {
			int ypos = point.get2().intValue()-1;
			int xpos = point.get1().intValue()-1;
			if (ypos >= p2.length) {
				return null;
			}
			if (xpos >= p2[ypos].length) {
				return null;
			}
			if (prev==null) {
				return p2[ypos][xpos];
			}
			
			return f.apply(prev, p2[ypos][xpos]);
		}
	};
	
	public static final Fun3<Point, Number[][], Number, Number> prodPoints = new AccOpPointsArray<Number>().apply(Math.mult);
}
